/*
 * Copyright (C) 2018 Zhejiang xiaominfo Technology CO.,LTD.
 * All rights reserved.
 * Official Web Site: http://www.xiaominfo.com.
 * Developer Web Site: http://open.xiaominfo.com.
 */

package com.github.xiaoymin.deploy;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import com.github.xiaoymin.deploy.model.DeployHelperServer;
import com.jcraft.jsch.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.List;
import java.util.concurrent.TimeUnit;

/***
 * Linux-SSH远程连接操作
 * @since:deploy-helper 1.0
 * @author <a href="mailto:xiaoymin@foxmail.com">xiaoymin@foxmail.com</a> 
 * 2020/07/02 11:40
 */
public class DeployHelperConnection {

    Logger logger= LoggerFactory.getLogger(DeployHelperConnection.class);

    private final DeployHelperServer deployHelperServer;

    private Session session;

    public DeployHelperConnection(DeployHelperServer deployHelperServer) {
        if (deployHelperServer==null){
            throw new IllegalArgumentException("deployHelperServer can't be empty");
        }
        this.deployHelperServer = deployHelperServer;
    }

    /**
     * 初始化Session连接
     * @throws JSchException ssh连接失败异常
     */
    public void openSession() throws JSchException {
        this.deployHelperServer.validate();
        this.session=new JSch().getSession(this.deployHelperServer.getUsername(),this.deployHelperServer.getHost(),this.deployHelperServer.getPort());
        this.session.setPassword(deployHelperServer.getPassword());
        this.session.setConfig("StrictHostKeyChecking", "no");
        this.session.connect();
        logger.info("open ssh Session Successful");
    }

    /**
     * 关闭ssh连接
     */
    public void closeSession(){
        if (this.session!=null){
            this.session.disconnect();
            logger.info("close ssh Session Successful");
        }
    }

    /**
     * 远程执行命令
     * @param command 命令
     * @throws JSchException 打开ssh时异常
     * @throws IOException io普通异常
     */
    public void command(String command) throws JSchException, IOException {
        if (StrUtil.isBlank(command)){
            throw new IllegalArgumentException("Command can't be empty!!!");
        }
        logger.info("command:{}",command);
        Channel channel=this.session.openChannel("exec");
        ChannelExec channelExec= (ChannelExec) channel;
        channelExec.setErrStream(System.err);
        channelExec.setCommand(command);
        channelExec.connect();
        InputStream in=channelExec.getInputStream();
        try {
            byte[] tmp = new byte[1024];
            while (true) {
                while (in.available() > 0) {
                    int i = in.read(tmp, 0, 1024);
                    if (i < 0){ break;}
                    logger.info(new String(tmp, 0, i));
                }
                if (channel.isClosed()) {
                    if (in.available() > 0) {continue;}
                    logger.info("exit-status: " + channel.getExitStatus());
                    break;
                }
                try {
                    TimeUnit.SECONDS.sleep(1L);
                } catch (Exception ee) {
                }
            }
        }finally {
            IoUtil.close(in);
            channelExec.disconnect();
        }
    }

    /**
     * 创建文件夹
     * @param path 文件夹路径
     * @param parent 父文件夹是否存在,如果该参数设置为true,则在执行创建文件夹命令时会自动创建父目录
     * @return 是否创建成功
     * @throws JSchException 打开ssh时异常
     * @throws IOException io异常
     */
    public boolean mkdir(String path,boolean parent) throws JSchException, IOException {
        if (StrUtil.isBlank(path)){
            throw new IllegalArgumentException("path can't be empty");
        }
        logger.info("mkdir:{}",path);
        boolean flag=false;
        if (exists(path)){
            flag=true;
        }else{
            String shell="mkdir ";
            if (parent){
                shell+="-p ";
            }
            shell+=path;
            shell(shell,true);
            flag=true;
        }
        return flag;
    }

    /**
     * 判断目标path是否存在,可以是文件或者文件夹
     * @param path 目录或文件
     * @return 是否存在boolean值
     * @throws JSchException 打开ssh时异常
     */
    public boolean exists(String path) throws JSchException {
        if (StrUtil.isBlank(path)){
            throw new IllegalArgumentException("path can't be empty");
        }
        boolean flag=false;
        Channel channel=this.session.openChannel("sftp");
        ChannelSftp channelSftp= (ChannelSftp) channel;
        channelSftp.connect();
        try {
            SftpATTRS sftpATTRS = channelSftp.lstat(path);
            flag=sftpATTRS!=null;
        } catch (SftpException e) {
            //此处异常需要捕获,jsch会向上抛异常,如果文件不存在的情况下
        }finally {
            channelSftp.exit();
        }
        return flag;
    }

    /**
     * 判断目标目录是否存在,如果不存在则创建目录
     * @param dir 检查目录
     * @throws JSchException 打开ssh时异常
     */
    public void existsAndCreate(String dir) throws JSchException {
        if (StrUtil.isBlank(dir)){
            throw new IllegalArgumentException("dir can't be empty");
        }
        logger.info("dir:{}",dir);
        Channel channel=this.session.openChannel("sftp");
        ChannelSftp channelSftp= (ChannelSftp) channel;
        channelSftp.connect();
        SftpATTRS sftpATTRS= null;
        try {
            sftpATTRS = channelSftp.lstat(dir);
        } catch (SftpException e) {
            //此处异常需要捕获,jsch会向上抛异常,如果文件不存在的情况下
            //e.printStackTrace();
            if (sftpATTRS==null){
                logger.info("create dir:{}",dir);
                //不存在,创建
                try {
                    channelSftp.mkdir(dir);
                } catch (SftpException sftpException) {
                    logger.error("create dir failed,message:{}",sftpException.getMessage());
                }
            }
        }finally {
            channelSftp.exit();
        }
    }

    /**
     * 执行shell
     * @param shell shell执行命令
     * @throws JSchException 打开ssh时异常
     * @throws IOException io普通异常
     */
    public void shell(String shell) throws JSchException, IOException {
        this.shell(shell,false);
    }

    /**
     * 执行批量Shell命令
     * @param shells shell命令集合
     * @param exit 是否执行完成shell脚本后退出当前会话,默认false
     * @throws JSchException 打开ssh时异常
     * @throws IOException io普通异常
     */
    public void shell(List<String> shells, boolean exit) throws JSchException, IOException {
        if (CollectionUtil.isEmpty(shells)){
            throw new IllegalArgumentException("Shells can't be empty");
        }
        ChannelShell channelShell= (ChannelShell) this.session.openChannel("shell");
        channelShell.connect();
        try(InputStream inputStream = channelShell.getInputStream();
            OutputStream outputStream = channelShell.getOutputStream()){
            PrintWriter printWriter = new PrintWriter(outputStream);
            for (String shell:shells){
                if (StrUtil.isNotBlank(shell)){
                    logger.info("Shell:{}",shell);
                    printWriter.println(shell);
                    try {
                        Thread.sleep(500L);
                    } catch (InterruptedException e) {
                        //ignore...
                    }
                }
            }
            if (exit){
                printWriter.println("exit");
            }
            printWriter.flush();
            BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
            String msg = null;
            while((msg = in.readLine())!=null){
                logger.info(msg);
            }
            IoUtil.close(in);
        }finally {
            if (channelShell!=null){
                channelShell.disconnect();
            }
        }

    }

    /**
     * 执行shell命令
     * @param shell shell脚本
     * @param exit 执行后是否退出标志
     * @throws JSchException 打开ssh时异常
     * @throws IOException io普通异常
     */
    public void shell(String shell,boolean exit) throws JSchException, IOException {
        if (StrUtil.isBlank(shell)){
            throw new IllegalArgumentException("Shell can't be empty!!!");
        }
        logger.info("Shell:{},exit:{}",shell,exit);
        ChannelShell channelShell= (ChannelShell) this.session.openChannel("shell");
        channelShell.connect();
        try(InputStream inputStream = channelShell.getInputStream();
            OutputStream outputStream = channelShell.getOutputStream()){
            PrintWriter printWriter = new PrintWriter(outputStream);
            printWriter.println(shell);
            if (exit){
                printWriter.println("exit");
            }
            printWriter.flush();
            BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
            String msg = null;
            while((msg = in.readLine())!=null){
                logger.info(msg);
            }
            IoUtil.close(in);
        }finally {
            if (channelShell!=null){
                channelShell.disconnect();
            }
        }
    }

    /**
     * 上传文件
     * @param sourceFile 源文件
     * @param targetFile 目标服务器目录存储目录文件
     * @throws JSchException 打开ssh时异常
     * @throws SftpException sftp协议异常
     */
    public void upload(String sourceFile,String targetFile) throws JSchException, SftpException {
        logger.info("start uplaod File,sourceFile:{},targetFile:{}",sourceFile,targetFile);
        Channel channel=this.session.openChannel("sftp");
        ChannelSftp channelSftp= (ChannelSftp) channel;
        channelSftp.connect();
        //覆盖策略
        channelSftp.put(sourceFile,targetFile,ChannelSftp.OVERWRITE);
        channelSftp.exit();
        logger.info("upload success.");
    }

}
